<?php
/*
define('EXEC_START_TIME', microtime(TRUE));
function auto_timer ($format_html = TRUE)
{
	if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') return;
	if (defined('EXEC_START_TIME')) //EXECUTE_START
	{
		$pased_time = microtime(TRUE) - EXEC_START_TIME;
		printf("<div style=\"text-align:center;color:red;\">Execute Time: %.6f</div>", $pased_time);
	}
}
register_shutdown_function('auto_timer');
//*/
/*


function debug () {
	$args = func_get_args();
	if (! headers_sent()) header('Content-type: text/html; charset=utf-8');
	echo "<pre>\n";
	foreach ($args as $value) {
		if (is_null($value)) {
			echo '[is_null]';
		}
		elseif (is_bool($value) || empty($value)) {
			var_dump($value);
		}
		else {
			print_r($value);
		}
		echo "\n";
	}
	$trace = debug_backtrace ();
	$next = array_merge ( array ('line' => '?', 'file' => '[internal]'), $trace [0] );
	unset($trace);
	echo '位置:',$next ['file'] . "\t第" . $next ['line'] . "行.\n";
	exit();
}
// */
if (! function_exists('debug'))
{

	function debug ()
	{
		//if (! class_exists('Debug', FALSE))include CORE_PATH . 'debug.php';
		$args = func_get_args();
		call_user_func_array(array(
			'Debug',
			'thin'
		), $args);
	}
}

function trace_debug ()
{
	//if (! class_exists('Debug', FALSE))include CORE_PATH . 'debug.php';
	$args = func_get_args();
	call_user_func_array(array(
		'Debug',
		'rich'
	), $args);
}
class Debug
{

	/**
	 * @var  string  character set of input and output
	 */
	public static $charset = 'utf-8';

	/**
	 * thin
	 * 调试参数中的变量并中断程序的执行，参数可以为任意多个,类型任意。
	 * 用于类库加载不完善时，简单显示调试信息
	 *
	 * <code>
	 * debug($var1,$obj1,$array1[,]................);
	 * debug($var1,'debug');
	 * </code>
	 */
	public static function thin ()
	{
		$args = func_get_args();
		if (! headers_sent()) header('Content-type: text/html; charset=utf-8');
		echo "<pre>\n----------------------------调试信息.---------------------------\n";
		foreach ($args as $value)
		{
			if (is_null($value))
			{
				echo '[is_null]';
			}
			elseif (is_bool($value) || empty($value))
			{
				var_dump($value);
			}
			else
			{
				print_r($value);
			}
			echo "\n";
		}
		$trace = debug_backtrace();
		array_splice($trace, 0, 2);
		$next = array_merge(array(
			'line' => '??',
			'file' => '[internal]',
			'class' => null,
			'function' => '[main]'
		), $trace[0]);
		if (strpos($next['file'], DOCROOT) !== false)
		{
			$next['file'] = str_replace(DOCROOT, '>DOCROOT' . DS, $next['file']);
		}
		echo "\n-----------------------------调试结束---------------------------\n文件位置:";
		echo $next['file'] . "\t第" . $next['line'] . "行.\n";
		if (defined('EXEC_START_TIME') && ! (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest')) register_shutdown_function(array(
			'Debug',
			'timer'
		), 6, true);
		die();
	}

	/**
	 * 计时器
	 *
	 * @param integer $decimals 显示精度
	 * @param boolean $echo		是否直接显示,为false则返回
	 * @return string
	 */
	public static function timer ($decimals = 6 ,$print = false)
	{
		$exec_time = number_format(microtime(TRUE) - EXEC_START_TIME, $decimals);
		if (! $print)
		{
			return $exec_time;
		}
		else
		{
			$memory = self::memory_usage($decimals, 'M');
			$str = '<p align="center" style="color:#999;font-size:9px;" class="kp-meta">
			Powered by Emvc! Parse Time:' . $exec_time . 's, using ' . $memory . 'MB of memory!</p>';
			echo $str;
			return;
		}
	}

	/**
	 * Quick debugging of any variable. Any number of parameters can be set.
	 *
	 * @return  string
	 */
	public static function dump ()
	{
		if (func_num_args() === 0) return;
		$params = func_get_args(); // Get params
		$output = array();
		foreach ($params as $var)
			$output[] = '<pre>(' . gettype($var) . ') ' . htmlspecialchars(print_r($var, TRUE)) . '</pre>';
		return implode("\n", $output);
	}

	public static function rich ()
	{
		$args = func_get_args();
		if ($args) $args = call_user_func_array(array(
			__CLASS__,
			'dump'
		), $args);
		$trace = debug_backtrace();
		array_splice($trace, 0, 2);
		$location = array_merge(array(
			'line' => '?',
			'file' => '[internal]'
		), array_shift($trace));
		$location['file'] = path::render($location['file']);
		$location = '<div><pre>调试位置:' . $location['file'] . '[' . $location['line'] . ']</pre></div>';
		$trace = self::backtrace($trace);
		$execution_time = self::timer();
		$memory = self::memory_usage(6, 'M') . 'MB';
		if (! headers_sent()) header('Content-type: text/html; charset=utf-8');
		if (class_exists('Path'))
		{
			$file = Loader::find('views.debuger');
			if ($file)
			{
				include $file;
			}
		}
		else
		{
			echo <<<HDOC
<style type="text/css">
h3 { color:#fff; font-size:16px; padding:8px 6px; margin:0 0 8px; background:#690; text-align:center;}
tt,pre{ font-family:monospace; padding:2px 4px; font-size:12px; color:#333;white-space:pre-wrap;white-space:-moz-pre-wrap;word-wrap:break-word;}
pre{background:#eaeee5;border:solid 0 #D6D8D1; border-width:0 1px 1px 0;}
</style>
<div style="margin:0 auto;width:90%;">
	<h3>调试信息</h3>
	$args $location
	<h3>Trace信息</h3>
	$trace
</div>
HDOC;
		}
		die();
	}

	public static function memory_usage ($decimals = 4 ,$unit = 'M')
	{
		static $func;
		if ($func === NULL) $func = function_exists('memory_get_usage');
		if (! $func) return 0;
		$memory = memory_get_usage();
		switch ($unit)
		{
			case 'M':
				$memory = $memory / 1024 / 1024;
				break;
			case 'K':
				$memory = $memory / 1024;
				break;
			case 'G':
				$memory = $memory / 1024 / 1024 / 1024;
			default:
		}
		return number_format($memory, $decimals);
	}

	/**
	 * Displays nice backtrace information.
	 * @see http://php.net/debug_backtrace
	 *
	 * @param   array   backtrace generated by an exception or debug_backtrace
	 * @return  string
	 */
	public static function backtrace ($trace)
	{
		if (! is_array($trace)) return;
		$output = array();
		foreach ($trace as $entry)
		{
			$temp = '<li>';
			if (isset($entry['file'])) $temp .= I18n::__('core.error_file_line', path::render($entry['file']), $entry['line']);
			$temp .= '<pre>';
			if (isset($entry['class'])) $temp .= $entry['class'] . $entry['type'];
			$temp .= $entry['function'] . '( ';
			if (isset($entry['args']) and is_array($entry['args']))
			{
				$sep = '';
				while ($arg = array_shift($entry['args']))
				{
					if (is_string($arg) and is_file($arg)) $arg = path::render($arg);
					$temp .= $sep . html::specialchars(print_r($arg, TRUE));
					//$temp .= $sep .htmlspecialchars(print_r($arg, TRUE), ENT_QUOTES, 'UTF-8', FALSE);
					$sep = ', ';
				}
			}
			$temp .= ' )</pre></li>';
			$output[] = $temp;
		}
		return '<ul class="backtrace">' . implode("\n", $output) . '</ul>';
	}

	function verbose ()
	{
		$args = func_get_args();
		ob_start();
		print_r($args);
		$output = ob_get_clean();
		return $output;
	}

	/**
	 * Removes application, system, modpath, or docroot from a filename,
	 * replacing them with the plain text equivalents. Useful for debugging
	 * when you want to display a shorter path.
	 *
	 * // Displays SYSPATH/classes/kohana.php
	 * echo Kohana::debug_path(Kohana::find_file('classes', 'kohana'));
	 *
	 * @param   string  path to debug
	 * @return  string
	 */
	public static function debug_path ($file)
	{
		if (strpos($file, APP_PATH) === 0)
		{
			$file = 'APP_PATH' . DIRECTORY_SEPARATOR . substr($file, strlen(APP_PATH));
		}
		elseif (strpos($file, MODULES_PATH) === 0)
		{
			$file = 'MODULES_PATH' . DIRECTORY_SEPARATOR . substr($file, strlen(MODULES_PATH));
		}
		elseif (strpos($file, SITE_PATH) === 0)
		{
			$file = 'SITE_PATH' . DIRECTORY_SEPARATOR . substr($file, strlen(SITE_PATH));
		}
		return $file;
	}

	/**
	 * Returns an HTML string, highlighting a specific line of a file, with some
	 * number of lines padded above and below.
	 *
	 * // Highlights the current line of the current file
	 * echo Kohana::debug_source(__FILE__, __LINE__);
	 *
	 * @param   string   file to open
	 * @param   integer  line number to highlight
	 * @param   integer  number of padding lines
	 * @return  string   source of file
	 * @return  FALSE    file is unreadable
	 */
	public static function debug_source ($file ,$line_number ,$padding = 5)
	{
		if (! $file or ! is_readable($file))
		{
			// Continuing will cause errors
			return FALSE;
		}
		// Open the file and set the line position
		$file = fopen($file, 'r');
		$line = 0;
		// Set the reading range
		$range = array(
			'start' => $line_number - $padding,
			'end' => $line_number + $padding
		);
		// Set the zero-padding amount for line numbers
		$format = '% ' . strlen($range['end']) . 'd';
		$source = '';
		while (($row = fgets($file)) !== FALSE)
		{
			// Increment the line number
			if (++ $line > $range['end']) break;
			if ($line >= $range['start'])
			{
				// Make the row safe for output
				$row = htmlspecialchars($row, ENT_NOQUOTES, self::$charset);
				// Trim whitespace and sanitize the row
				$row = '<span class="number">' . sprintf($format, $line) . '</span> ' . $row;
				if ($line === $line_number)
				{
					// Apply highlighting to this row
					$row = '<span class="line highlight">' . $row . '</span>';
				}
				else
				{
					$row = '<span class="line">' . $row . '</span>';
				}
				// Add to the captured source
				$source .= $row;
			}
		}
		// Close the file
		fclose($file);
		return '<pre class="source"><code>' . $source . '</code></pre>';
	}

	/**
	 * Returns an array of HTML strings that represent each step in the backtrace.
	 *
	 * // Displays the entire current backtrace
	 * echo implode('<br/>', Kohana::trace());
	 *
	 * @param   string  path to debug
	 * @return  string
	 */
	public static function trace (array $trace = NULL)
	{
		if ($trace === NULL)
		{
			// Start a new trace
			$trace = debug_backtrace();
		}
		// Non-standard function calls
		$statements = array(
			'include',
			'include_once',
			'require',
			'require_once'
		);
		$output = array();
		foreach ($trace as $step)
		{
			if (! isset($step['function']))
			{
				// Invalid trace step
				continue;
			}
			if (isset($step['file']) and isset($step['line']))
			{
				// Include the source of this step
				$source = self::debug_source($step['file'], $step['line']);
			}
			if (isset($step['file']))
			{
				$file = $step['file'];
				if (isset($step['line']))
				{
					$line = $step['line'];
				}
			}
			// function()
			$function = $step['function'];
			if (in_array($step['function'], $statements))
			{
				if (empty($step['args']))
				{
					// No arguments
					$args = array();
				}
				else
				{
					// Sanitize the file path
					$args = array(
						$step['args'][0]
					);
				}
			}
			elseif (isset($step['args']))
			{
				if (! function_exists($step['function']) or strpos($step['function'], '{closure}') !== FALSE)
				{
					// Introspection on closures or language constructs in a stack trace is impossible
					$params = NULL;
				}
				else
				{
					if (isset($step['class']))
					{
						if (method_exists($step['class'], $step['function']))
						{
							$reflection = new ReflectionMethod($step['class'], $step['function']);
						}
						else
						{
							$reflection = new ReflectionMethod($step['class'], '__call');
						}
					}
					else
					{
						$reflection = new ReflectionFunction($step['function']);
					}
					// Get the function parameters
					$params = $reflection->getParameters();
				}
				$args = array();
				foreach ($step['args'] as $i => $arg)
				{
					if (isset($params[$i]))
					{
						// Assign the argument by the parameter name
						$args[$params[$i]->name] = $arg;
					}
					else
					{
						// Assign the argument by number
						$args[$i] = $arg;
					}
				}
			}
			if (isset($step['class']))
			{
				// Class->method() or Class::method()
				$function = $step['class'] . $step['type'] . $step['function'];
			}
			$output[] = array(
				'function' => $function,
				'args' => isset($args) ? $args : NULL,
				'file' => isset($file) ? $file : NULL,
				'line' => isset($line) ? $line : NULL,
				'source' => isset($source) ? $source : NULL
			);
			unset($function, $args, $file, $line, $source);
		}
		return $output;
	}

	/**
	 * Helper for Kohana::dump(), handles recursion in arrays and objects.
	 *
	 * @param   mixed    variable to dump
	 * @param   integer  maximum length of strings
	 * @param   integer  recursion level (internal)
	 * @return  string
	 */
	protected static function _dump (& $var ,$length = 128 ,$level = 0)
	{
		if ($var === NULL)
		{
			return '<small>NULL</small>';
		}
		elseif (is_bool($var))
		{
			return '<small>bool</small> ' . ($var ? 'TRUE' : 'FALSE');
		}
		elseif (is_float($var))
		{
			return '<small>float</small> ' . $var;
		}
		elseif (is_resource($var))
		{
			if (($type = get_resource_type($var)) === 'stream' and $meta = stream_get_meta_data($var))
			{
				$meta = stream_get_meta_data($var);
				if (isset($meta['uri']))
				{
					$file = $meta['uri'];
					if (function_exists('stream_is_local'))
					{
						// Only exists on PHP >= 5.2.4
						if (stream_is_local($file))
						{
							$file = Kohana::debug_path($file);
						}
					}
					return '<small>resource</small><span>(' . $type . ')</span> ' . htmlspecialchars($file, ENT_NOQUOTES, Kohana::$charset);
				}
			}
			else
			{
				return '<small>resource</small><span>(' . $type . ')</span>';
			}
		}
		elseif (is_string($var))
		{
			// Clean invalid multibyte characters. iconv is only invoked
			// if there are non ASCII characters in the string, so this
			// isn't too much of a hit.
			$var = UTF8::clean($var);
			if (UTF8::strlen($var) > $length)
			{
				// Encode the truncated string
				$str = htmlspecialchars(UTF8::substr($var, 0, $length), ENT_NOQUOTES, Kohana::$charset) . '&nbsp;&hellip;';
			}
			else
			{
				// Encode the string
				$str = htmlspecialchars($var, ENT_NOQUOTES, Kohana::$charset);
			}
			return '<small>string</small><span>(' . strlen($var) . ')</span> "' . $str . '"';
		}
		elseif (is_array($var))
		{
			$output = array();
			// Indentation for this variable
			$space = str_repeat($s = '    ', $level);
			static $marker;
			if ($marker === NULL)
			{
				// Make a unique marker
				$marker = uniqid("\x00");
			}
			if (empty($var))
			{ // Do nothing
			}
			elseif (isset($var[$marker]))
			{
				$output[] = "(\n$space$s*RECURSION*\n$space)";
			}
			elseif ($level < 5)
			{
				$output[] = "<span>(";
				$var[$marker] = TRUE;
				foreach ($var as $key => & $val)
				{
					if ($key === $marker) continue;
					if (! is_int($key))
					{
						$key = '"' . htmlspecialchars($key, ENT_NOQUOTES, self::$charset) . '"';
					}
					$output[] = "$space$s$key => " . Kohana::_dump($val, $length, $level + 1);
				}
				unset($var[$marker]);
				$output[] = "$space)</span>";
			}
			else
			{
				// Depth too great
				$output[] = "(\n$space$s...\n$space)";
			}
			return '<small>array</small><span>(' . count($var) . ')</span> ' . implode("\n", $output);
		}
		elseif (is_object($var))
		{
			// Copy the object as an array
			$array = (array) $var;
			$output = array();
			// Indentation for this variable
			$space = str_repeat($s = '    ', $level);
			$hash = spl_object_hash($var);
			// Objects that are being dumped
			static $objects = array();
			if (empty($var))
			{ // Do nothing
			}
			elseif (isset($objects[$hash]))
			{
				$output[] = "{\n$space$s*RECURSION*\n$space}";
			}
			elseif ($level < 10)
			{
				$output[] = "<code>{";
				$objects[$hash] = TRUE;
				foreach ($array as $key => & $val)
				{
					if ($key[0] === "\x00")
					{
						// Determine if the access is protected or protected
						$access = '<small>' . ($key[1] === '*' ? 'protected' : 'private') . '</small>';
						// Remove the access level from the variable name
						$key = substr($key, strrpos($key, "\x00") + 1);
					}
					else
					{
						$access = '<small>public</small>';
					}
					$output[] = "$space$s$access $key => " . Kohana::_dump($val, $length, $level + 1);
				}
				unset($objects[$hash]);
				$output[] = "$space}</code>";
			}
			else
			{
				// Depth too great
				$output[] = "{\n$space$s...\n$space}";
			}
			return '<small>object</small> <span>' . get_class($var) . '(' . count($array) . ')</span> ' . implode("\n", $output);
		}
		else
		{
			return '<small>' . gettype($var) . '</small> ' . htmlspecialchars(print_r($var, TRUE), ENT_NOQUOTES, Kohana::$charset);
		}
	}

	/**
	 * Returns an HTML string of debugging information about any number of
	 * variables, each wrapped in a "pre" tag:
	 *
	 * // Displays the type and value of each variable
	 * echo Kohana::debug($foo, $bar, $baz);
	 *
	 * @param   mixed   variable to debug
	 * @param   ...
	 * @return  string
	 */
	public static function xdebug ()
	{
		if (func_num_args() === 0) return;
		// Get all passed variables
		$variables = func_get_args();
		$output = array();
		foreach ($variables as $var)
		{
			$output[] = Kohana::_dump($var, 1024);
		}
		return '<pre class="debug">' . implode("\n", $output) . '</pre>';
	}

	/**
	 * Get a single line of text representing the exception:
	 *
	 * Error [ Code ]: Message ~ File [ Line ]
	 *
	 * @param   object  Exception
	 * @return  string
	 */
	public static function exception_text (Exception $e)
	{
		return sprintf('%s [ %s ]: %s ~ %s [ %d ]', get_class($e), $e->getCode(), strip_tags($e->getMessage()), self::debug_path($e->getFile()), $e->getLine());
	}

	/**
	 * Convert special characters to HTML entities. All untrusted content
	 * should be passed through this method to prevent XSS injections.
	 *
	 * echo HTML::chars($username);
	 *
	 * @param   string   string to convert
	 * @param   boolean  encode existing entities
	 * @return  string
	 */
	public static function chars ($value ,$double_encode = TRUE)
	{
		return htmlspecialchars((string) $value, ENT_QUOTES, self::$charset, $double_encode);
	}
}
